#include <Windows.h>
#include "OwnerDrawMenu.h"

#pragma comment(lib, "Msimg32.lib")

using namespace std;

map<HWND, CMenuOwnerDraw *> CMenuOwnerDraw::ms_vPtrSet;

CMenuOwnerDraw::CMenuOwnerDraw() {
    m_bInit = FALSE;
    m_hParentWnd = NULL;
    m_pOldProcPtr = NULL;
    m_hMenu = NULL;

    m_dwDefaultRgb = 0;
    m_dwSelectRgb = 0;
    m_hBkBrush = NULL;
    m_hSelBrush = NULL;
    m_hFont = NULL;
    m_dwWidth = 0;
    m_dwHigh = 0;
}

CMenuOwnerDraw::~CMenuOwnerDraw() {
}

void CMenuOwnerDraw::CreateMyPopupMenu(HWND hParentWnd) {
    m_hParentWnd = hParentWnd;
    m_hMenu = ::CreatePopupMenu();

    m_hBkBrush = CreateSolidBrush(RGB(240, 240, 240));
    m_hSelBrush = CreateSolidBrush(RGB(145, 201, 247));
    m_pOldProcPtr = SetWindowLongPtrA(hParentWnd, GWLP_WNDPROC, (LONG_PTR)WindowProc);
    ms_vPtrSet[m_hParentWnd] = this;
}

void CMenuOwnerDraw::SetMenuFont(HFONT hFont) {
    m_hFont = hFont;
}

void CMenuOwnerDraw::ShowPupupMenu(DWORD x, DWORD y) {
    TrackPopupMenu(m_hMenu, TPM_CENTERALIGN, x, y, 0, m_hParentWnd, 0);
    DestroyMenu(m_hMenu);

    ms_vPtrSet.erase(m_hParentWnd);
    SetWindowLongPtrA(m_hParentWnd, GWLP_WNDPROC, m_pOldProcPtr);
    DeleteObject((HGDIOBJ)m_hBkBrush), m_hBkBrush = NULL;
    DeleteObject((HGDIOBJ)m_hSelBrush), m_hSelBrush = NULL;
}

void CMenuOwnerDraw::SetItemSize(DWORD dwWidth, DWORD dwHigh) {
    m_dwWidth = dwWidth, m_dwHigh = dwHigh;
}

void CMenuOwnerDraw::SetMenuBkColour(DWORD dwDefaultRgb, DWORD dwSelectRgb) {
    m_dwDefaultRgb = dwDefaultRgb;
    m_dwSelectRgb =  dwSelectRgb;
}

void CMenuOwnerDraw::AppendMenuItem(DWORD dwId, HBITMAP hBmp, LPCSTR szText) {
    MenuItemInfo vInfo;
    vInfo.dwCtrlId = dwId, vInfo.hBitmap = hBmp;
    vInfo.strText = szText, vInfo.nType = EM_MENU_ITEM;
    m_vMenuCache[dwId] = vInfo;

    AppendMenuA(m_hMenu, MF_STRING | MF_ENABLED | MF_OWNERDRAW , dwId, szText);
}

void CMenuOwnerDraw::AppendSplitLine() {
    AppendMenuA(m_hMenu, MF_SEPARATOR, 0, NULL);
}

INT_PTR CMenuOwnerDraw::OnMeasureItem(HWND hWnd, WPARAM wp, LPARAM lp) {
    LPMEASUREITEMSTRUCT pMenuItem = (LPMEASUREITEMSTRUCT)lp;

    if (pMenuItem->CtlType == ODT_MENU) {
        if (m_vMenuCache.end() != m_vMenuCache.find(pMenuItem->itemID)) {
            pMenuItem->itemHeight = m_dwHigh;
            pMenuItem->itemWidth = m_dwWidth;
        }
    }
    return 1;
}

BOOL CMenuOwnerDraw::DrawItemImage(HDC hMemoryDC, HBITMAP hImage) const {
    HDC hMemoryDC2 = CreateCompatibleDC(hMemoryDC);

    HBITMAP hOldBmp = (HBITMAP)SelectObject(hMemoryDC2, hImage);
    TransparentBlt(hMemoryDC, 2, 2, 16, 16, hMemoryDC2, 0, 0, 16, 16, RGB(192,192,192));

    SelectObject(hMemoryDC2, hOldBmp);
    DeleteObject(hMemoryDC2);
    return TRUE;
}

INT_PTR CMenuOwnerDraw::OnDrawItem(HWND hWnd, WPARAM wp, LPARAM lp) {
    LPDRAWITEMSTRUCT pDrawItem = (LPDRAWITEMSTRUCT)lp;
    if (pDrawItem->hwndItem == (HWND)m_hMenu) {

        map<DWORD, MenuItemInfo>::const_iterator it = m_vMenuCache.find(pDrawItem->itemID);

        if (it != m_vMenuCache.end()) {
            DWORD dwWidth = pDrawItem->rcItem.right - pDrawItem->rcItem.left;
            DWORD dwHigh = pDrawItem->rcItem.bottom - pDrawItem->rcItem.top;

            HDC hMemoryDC = CreateCompatibleDC(pDrawItem->hDC);
            HBITMAP hMemoryBmp = CreateCompatibleBitmap(pDrawItem->hDC, dwWidth, dwHigh);
            HBITMAP hOldBmp = (HBITMAP)SelectObject(hMemoryDC, hMemoryBmp);

            const MenuItemInfo &vMenuInfo = it->second;
            RECT rtMemoryDC = {0};
            rtMemoryDC.right = dwWidth;
            rtMemoryDC.bottom = dwHigh;

            HBRUSH hBrush= NULL;
            if (pDrawItem->itemState & ODS_SELECTED) {
                hBrush = m_hSelBrush;
            } else {
                hBrush = m_hBkBrush;
            }
            FillRect(hMemoryDC, &rtMemoryDC, hBrush);
            SetBkMode(hMemoryDC, 1);

            HFONT hOldFont = (HFONT)SelectObject(hMemoryDC, m_hFont);
            char szFontName[128] = {0};
            GetTextFaceA(hMemoryDC, 128, szFontName);

            rtMemoryDC.left += 32;
            DrawTextA(hMemoryDC, vMenuInfo.strText.c_str(), -1, &rtMemoryDC, DT_SINGLELINE | DT_VCENTER);
            SelectObject(hMemoryDC, hOldFont);

            if (vMenuInfo.hBitmap) {
                DrawItemImage(hMemoryDC, vMenuInfo.hBitmap);
            }

            BitBlt(pDrawItem->hDC, pDrawItem->rcItem.left, pDrawItem->rcItem.top, dwWidth, dwHigh, hMemoryDC, 0, 0, SRCCOPY);
            SelectObject(hMemoryDC, hOldBmp);
            DeleteObject(hMemoryBmp);
            DeleteObject(hMemoryDC);
        }
    }
    return 0;
}

LRESULT CMenuOwnerDraw::WindowProc(HWND hwnd, UINT nMsg, WPARAM wp, LPARAM lp) {
    map<HWND, CMenuOwnerDraw *>::iterator it;
    it = ms_vPtrSet.find(hwnd);

    if (it == ms_vPtrSet.end()) {
        return CallWindowProc(DefWindowProc, hwnd, nMsg, wp, lp);
    } else {
        CMenuOwnerDraw *pThis = it->second;

        if (nMsg == WM_MEASUREITEM) {
            pThis->OnMeasureItem(hwnd, wp, lp);
            return 1;
        } else if (nMsg == WM_DRAWITEM) {
            pThis->OnDrawItem(hwnd, wp, lp);
            return 1;
        } else if (nMsg == WM_CTLCOLORSTATIC) {
            return FALSE;
        }
        return CallWindowProc((WNDPROC)pThis->m_pOldProcPtr, hwnd, nMsg, wp, lp);
    }
}